/*本文件实现命令行解析主功能，历史记录，tab联想.
 * 外部接口有：
 * 01. cli_init:初始化本模块；
 * 02. getcmd:从命令行输入得到一段命令字符串;
 * 03. omCmdlineParse: 从命令字符串解析得到命令字和各参数
 * 04. 本文件可以理解为一个独立的库 */

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <termio.h>
#include <pthread.h>
#include <stdlib.h>
#include "../include/cli.h"

/* 定义函数返回码，本文件内使用*/
#define GETCH_CHAR   1
#define GETCH_UNCHAR 2
#define GETCH_ERROR  0

/* 定义log，本文件内使用 */
#define OM_LOG_LEVEL_ERRO      1
#define OM_LOG_LEVEL_INFO      2
#define OM_LOG_LEVEL_DEBG      3

#define OM_LOG(_logLevel,...)                                        \
    if(g_omlogLevel >= _logLevel)                                  \
        if(OM_LOG_LEVEL_ERRO == _logLevel)                            \
            {printf("OM-LOG ERRO: "__VA_ARGS__);   printf("\n");}     \
        else if(OM_LOG_LEVEL_INFO == _logLevel)                       \
            {printf("OM-LOG INFO: "__VA_ARGS__);   printf("\n");}     \
        else if(OM_LOG_LEVEL_DEBG == _logLevel)                       \
            {printf("OM-LOG DEBG: "__VA_ARGS__);   printf("\n");}

/* 定义格式化宏 */
#define PT_MOVELEFT(y)     \
       printf("\033[%dD", (y)) ; \
       fflush(stdout)

#define PT_MOVERIGHT(y)   \
        printf("\033[%dC",(y)) ;\
        fflush(stdout)

#define PT_BACKSPACE    \
        printf("\b \b");   \
        fflush(stdout)

#define PT_PRINTCHAR(c)    \
        printf("%c",c);    \
	    fflush(stdout)

#define PT_PRINTS(s)    \
        printf("%s",s);    \
	    fflush(stdout)

//定义命令字符串数组
const char *g_cmlOrdStrTable[] = { SUPPORT_CMD_LIST };

//自动计算的命令数量
const int g_cmlOrdNum = sizeof(g_cmlOrdStrTable)/sizeof(g_cmlOrdStrTable[0]);

/* 命令历史记录链表，双向循环链表 */
typedef struct tagOmCmlHistory
{
    char *cmd;
    struct tagOmCmlHistory *pNext,*pPrev;
}OmCmlHistoryT;

/* 用于tab联想结果记录 */
typedef struct tagOmCmlHistoryList
{
    OmCmlHistoryT *pHead,*pCurrent;
    int n;
    char firstdownflag;
}OmCmlHistoryListT;

// linux标准输入
static struct termios tty;
//全局历史命令列表
OmCmlHistoryListT  *g_hisList;


//判断给出的命令字是否在命令列表中
bool isInCmdListTable(char cmd[]){

	for(int i=0; i<g_cmlOrdNum;i++){
		if(strcmp(cmd, g_cmlOrdStrTable[i]) == 0){
			return true;
		}
	}
	return false;
}

//命令历史链表创建
OmCmlHistoryListT *omCmlHistoryListCreate()
{
    OmCmlHistoryListT *thiz =NULL;
    thiz = (OmCmlHistoryListT *)malloc(sizeof(OmCmlHistoryListT));
    if(!thiz)
    {
        return NULL;
    }

    thiz->pHead = NULL;
    thiz->pCurrent = NULL;
    thiz->n = 0;
    thiz->firstdownflag =1;

    return thiz;
}

//命令历史链表插入
int omCmlHistoryListInsert(OmCmlHistoryListT *thiz, char cmd[])
{
    OmCmlHistoryT *pTemp = NULL;

    if(strlen(cmd) == 0)
    {
        return 0;
    }

    if(0 == thiz->n)
    {

        thiz->pHead = (OmCmlHistoryT *)malloc(sizeof(OmCmlHistoryT));
        if(!thiz->pHead)
        {
            printf("malloc error\n");
            return -1;
        }

        memset(thiz->pHead,0,sizeof(OmCmlHistoryT));

        thiz->pHead->cmd = (char *)malloc(OM_CML_CHAR_MAX);
        if(!thiz->pHead->cmd)
        {
            printf("malloc error\n");
            return -1;
        }

        memset(thiz->pHead->cmd,0,OM_CML_CHAR_MAX);
        strcpy(thiz->pHead->cmd,cmd);

        thiz->pCurrent = thiz->pHead;
        thiz->n ++;

    }
    else if(thiz->n < OM_CML_HISTORY_MAX)
    {
        pTemp = (OmCmlHistoryT *)malloc(sizeof(OmCmlHistoryT));
        if(!pTemp)
            return -1;
        memset(pTemp,0,sizeof(sizeof(OmCmlHistoryT)));

        pTemp->cmd = (char*)malloc(OM_CML_CHAR_MAX);
        if(!pTemp->cmd)
            return -1;

        memset(pTemp->cmd,0,OM_CML_CHAR_MAX);
        strcpy(pTemp->cmd,cmd);

        if(thiz->pHead->pNext == NULL)
        {
            thiz->pHead->pNext = pTemp;
            thiz->pHead->pPrev = pTemp;
            pTemp->pNext = thiz->pHead;
            pTemp->pPrev = thiz->pHead;
        }
        else
        {
            pTemp->pNext = thiz->pHead->pNext;
            pTemp->pPrev = thiz->pHead;

            thiz->pHead->pNext->pPrev = pTemp;
            thiz->pHead->pNext = pTemp;
        }
        thiz->n ++;
    }
    else
    {
        memset(thiz->pHead->cmd,0,OM_CML_CHAR_MAX);
        strcpy(thiz->pHead->cmd,cmd);

        thiz->pHead = thiz->pHead->pPrev;
        thiz->pCurrent = thiz->pHead;
    }

    int i =0;
    if(thiz->n > 1)
    {
        pTemp = thiz->pHead->pNext->pNext;
        for(i=0; i< thiz->n-1; i++)
        {
            if(strcmp(pTemp->cmd, cmd) == 0)
            {
                break;
            }
            pTemp = pTemp->pNext;
        }

        if(pTemp != thiz->pHead->pNext)
        {
            if(pTemp == thiz->pHead)
            {                
                thiz->pHead = pTemp->pPrev;
                thiz->pCurrent = thiz->pHead;
            }

            pTemp->pPrev->pNext = pTemp->pNext;
            pTemp->pNext->pPrev = pTemp->pPrev;

            free(pTemp);
            thiz->n -- ;
        }
    }

    //print list for test
    #ifdef CML_DEBUG
    OM_LOG(OM_LOG_LEVEL_DEBG,"history list(which n is %d): ",thiz->n);

    if(thiz->n ==1)
    {
        OM_LOG(OM_LOG_LEVEL_DEBG,"history[%d] = %s",i,thiz->pHead->cmd);
    }
    else
    {
        pTemp = thiz->pHead->pNext;
        for(i=0; i<thiz->n;i++)
        {
            OM_LOG(OM_LOG_LEVEL_DEBG,"history[%d] = %s",i,pTemp->cmd);
            pTemp = pTemp->pNext;
        }

    }
    #endif

    return 0;

}

//命令历史恢复当前
int omCmlHistoryResetCur(OmCmlHistoryListT *thiz)
{
    thiz->pCurrent = thiz->pHead;
    thiz->firstdownflag =1;

    return 0;
}

//命令历史向上
char *omCmlHistoryUp(OmCmlHistoryListT *thiz)
{
    if(thiz->n == 0)
    {
        return NULL;
    }
    else if(thiz->n ==1)
    {
        return thiz->pHead->cmd;
    }
    else
    {

        thiz->pCurrent = thiz->pCurrent->pNext;

        return thiz->pCurrent->cmd;
    }
}

//命令历史向下
char *omCmlHistoryDown(OmCmlHistoryListT *thiz)
{
    if(thiz->n == 0)
    {
        return NULL;
    }
    else if(thiz->n ==1)
    {
        return thiz->pHead->cmd;
    }
    else
    {
        if(thiz->firstdownflag)
        {
            thiz->firstdownflag = 0;
        }
        else
        {
            thiz->pCurrent = thiz->pCurrent->pPrev;
        }

        return thiz->pCurrent->cmd;

    }
}

//联想
int omCmlFindOrderbyFirstPart(char *cmd,char *ret)
{
    int i,j,n=0,len_part,len;
    char ordInd[OM_CML_ORDER_NUM_MAX] = {0};

    len_part = strlen(cmd);

    for(i=0; i<g_cmlOrdNum; i++)
    {

        len = strlen(g_cmlOrdStrTable[i]);
        if(len_part < len)
        {
            for(j=0;j<len_part; j++)
            {
                if(g_cmlOrdStrTable[i][j] != cmd[j])
                {
                    break;
                }
            }

            if(j == len_part)
            {
                strcpy(ret,g_cmlOrdStrTable[i]);
                ordInd[n] = i;
                n++;
            }

        }
    }

    if(n > 1)
    {
        for(j=len_part; j<OM_CML_ORDER_CHAR_MAX;j++)
        {
            for(i=0; i<n; i++)
            {
                len = strlen(g_cmlOrdStrTable[(int)ordInd[i]]);
                if(len <= j)
                {
                    ret[j] = 0;
                    return n;
                }
                else
                {
                    if(g_cmlOrdStrTable[(int)ordInd[i]][j] != ret[j])
                    {
                        ret[j] = 0;
                        return n;
                    }
                }
            }
        }
        /*��ȫ����*/
        ret[j] =0;
    }

    return n;
}

//插入字符
int omCmlInsertchar(char *cmd,int cur_i,int cmd_i,char c)
{
    char cnncmd[OM_CML_CHAR_MAX];

    if(cmd_i < 0 || cur_i < 0)
        return -1;

    if(cur_i > cmd_i)
        return -1;
    else if(cmd_i == cur_i)
        cmd[cmd_i] = c;
    else
    {
        strcpy(cnncmd, cmd+cur_i);
        cmd[cur_i] = c;
        cmd[cur_i+1] = '\0';
        strcat(cmd,cnncmd);
    }

    return 0;
}

//删除字符
int omCmlDeletechar(char *cmd,int cur_i,int cmd_i)
{
    char cnncmd[OM_CML_CHAR_MAX];

    if(cmd_i < 0 || cur_i < 0)
        return -1;

    if(cur_i > cmd_i)
        return -1;
    else if(cmd_i == cur_i)
        return 0;
    else
    {
        strcpy(cnncmd, cmd+cur_i+1);
        cmd[cur_i] = '\0';
        strcat(cmd,cnncmd);
    }

    return 0;
}

/*设置key，恢复key*/
void set_key()
{
  struct termios new_tty;
  tcgetattr(0,&tty);
  new_tty=tty;
  new_tty.c_lflag&=~(ICANON|ECHO|ISIG);
  new_tty.c_cc[VTIME]=0;
  new_tty.c_cc[VMIN]=1;
  tcsetattr(0,TCSANOW,&new_tty);
  return;
}
void reset_key()
{
  tcsetattr(0,TCSANOW,&tty);
  return;
}

/*
获取字符
*/
int getch(char *c)
{
    int i;
    char ch[3];

    i = read(0, ch, 3) ;

    if( i == 1)
    {
         *c = ch[0];
        return GETCH_CHAR;
    }

    if(i == 3)
    {
        *c = ch[2];
        return GETCH_UNCHAR;
    }

    return GETCH_ERROR;
}


PF_CtrlvProc g_pfCtrlvProc = 0;
//获取命令主函数
int getcmd(char *cmd)
{
    int getch_rc;
    int cmd_i,cur_i,j;
    char c;
    char * hiscmd;
    char strOrd[OM_CML_ORDER_CHAR_MAX];
    char cmdtemp[OM_CML_CHAR_MAX];
    char delFlag = 0;/*unbuntu delete�����º����~�����������*/

    set_key();

    OmCmlHistoryListT *hisList = g_hisList;

    cmd_i =0;
    cur_i =0;

    while(1)
	{
        getch_rc = getch(&c);

        if(cmd_i >= OM_CML_CHAR_MAX -1)
        {
            printf("--input command out of range(max is %d).\n",OM_CML_CHAR_MAX);
            reset_key();
            return 0;
        }

        if(getch_rc == GETCH_CHAR)
        {
            switch(c)
            {
                case 0x0a: /*enter*/
                    cmd[cmd_i] = '\0';
                    PT_PRINTCHAR('\n');
                    omCmlHistoryListInsert(hisList,cmd);
                    omCmlHistoryResetCur(hisList);
                    reset_key();
                    return 0;
                case 0x1b: /* esc */
                    reset_key();
                    exit(0);
                case 0x03: /*ctrl +c */
                    reset_key();
                    exit(0);
                case 0x16: /*22 ctrl +v */
                	char cp[OM_CML_CHAR_MAX];//用于保存clipboard中的字符
                	if(g_pfCtrlvProc){
                		g_pfCtrlvProc(cp,OM_CML_CHAR_MAX);
                	    int cp_i=0;
                	    while(cp[cp_i]){
                	    	cmd[cmd_i] = 0;
							omCmlInsertchar(cmd, cur_i,cmd_i,cp[cp_i]);
							if(cur_i == cmd_i)
							{
								PT_PRINTCHAR(cp[cp_i]);
							}
							else
							{

								for(j=cur_i;j<cmd_i+1;j++)
								{
									printf(" \b");
									PT_PRINTCHAR(cmd[j]);
								}
								PT_MOVELEFT(cmd_i-cur_i);
							}

							cmd_i ++;
							cur_i ++;
							cp_i ++;
                	    }//while
                	}//if
                	break;

                case 0x7f: /* backspace */
                    if(cur_i)
                    {
                        if(cur_i == cmd_i)
                        {

                            cmd[cmd_i-1] = 0;
                            PT_BACKSPACE;
                            cmd_i --;
                            cur_i --;
                        }
                        else
                        {
                            cmd[cmd_i] = 0;
                            strcpy(cmdtemp,cmd+cur_i);
                            cmd[cur_i-1] = '\0';
                            strcat(cmd,cmdtemp);
                            printf("\b \b");
                            for(j = cur_i-1;j<cmd_i-1;j++)
                            {
                                printf("%c \b",(cmd[j]));
                            }
                            fflush(stdout);
                            PT_MOVELEFT(cmd_i-cur_i);
                            cur_i --;
                            cmd_i --;
                        }
                    }

                    break;
                case 0x09: /*tab*/
                    cmd[cmd_i] = 0;
                    if(0 != omCmlFindOrderbyFirstPart(cmd,strOrd))
                    {
                        for(j=cmd_i;j<(int)strlen(strOrd);j++)
                        {
                            cmd[j] = strOrd[j];
                            PT_PRINTCHAR(cmd[j]);
                        }
                        cmd_i = j;
                        cur_i = j;
                    }

                    break;
                case 0x08:/*delete*/
                    if(cur_i < cmd_i)
                    {
                        cmd[cmd_i] =0;
                        omCmlDeletechar(cmd, cur_i, cmd_i);
                        for(j =cur_i; j<cmd_i -1; j++)
                        {
                            printf(" \b");
                            PT_PRINTCHAR(cmd[j]);
                        }
                        PT_PRINTS(" \b");
                        if(cmd_i -cur_i -1 >0)
                        {
                            PT_MOVELEFT(cmd_i -cur_i -1);
                        }

                        cmd_i --;
                    }

                    break;
                default:
                    //printf("this key 1value = %x\n",c);
                    if(delFlag == 1 && c == 0x7e)
                    {
                        delFlag =0;
                        break;
                    }
                    cmd[cmd_i] = 0;
                    omCmlInsertchar(cmd, cur_i,cmd_i,c);
                    if(cur_i == cmd_i)
                    {
                        PT_PRINTCHAR(c);
                    }
                    else
                    {

                        for(j=cur_i;j<cmd_i+1;j++)
                        {
                            printf(" \b");
                            PT_PRINTCHAR(cmd[j]);
                        }
                        PT_MOVELEFT(cmd_i-cur_i);
                    }

                    cmd_i ++;
                    cur_i ++;
                    break;
            }

        }
        else if(getch_rc == GETCH_UNCHAR)
        {
            switch(c)
			{
				case 'A':  /* KeyUpClick */

                    hiscmd = omCmlHistoryUp(hisList);

                    if(hiscmd != NULL)
                    {
                        if(cur_i < cmd_i)
                        {
                            PT_MOVERIGHT(cmd_i - cur_i);
                            cur_i = cmd_i;
                        }

    					for(j=0;j<cmd_i;j++)
    					{
    						PT_BACKSPACE;
    					}
                        PT_PRINTS(hiscmd);
                        strcpy(cmd,hiscmd);
                        cmd_i = strlen(cmd);
                        cur_i = cmd_i;
                    }

					break;
				case 'B':  /* KeyDownClick */
                    hiscmd = omCmlHistoryDown(hisList);
                    if(hiscmd != NULL)
                    {                        
                        if(cur_i < cmd_i)
                        {
                            PT_MOVERIGHT(cmd_i - cur_i);
                            cur_i = cmd_i;
                        }
                
                        for(j=0;j<cmd_i;j++)
                        {
                            PT_BACKSPACE;
                        }
                        PT_PRINTS(hiscmd);
                        strcpy(cmd,hiscmd);
                        cmd_i = strlen(cmd);
                        cur_i = cmd_i;
                    }

					break;
				case 'C':  /* KeyRightClick */
                    if(cur_i < cmd_i)
                    {
                        PT_MOVERIGHT(1);
                        cur_i ++;
                    }
					break;
				case 'D':  /* KeyLeftClick */
                    if(cur_i)
                    {
                        cur_i --;
                        PT_MOVELEFT(1);
                    }
					break;
                case 0x48: /*home*/
                    if(cur_i !=0)
                    {
                        PT_MOVELEFT(cur_i);
                        cur_i =0;
                    }
                    break;
                case 0x46:/*end*/
                    if(cur_i != cmd_i)
                    {
                        PT_MOVERIGHT(cmd_i-cur_i);
                        cur_i = cmd_i;
                    }
                    break;
                case 0x33:/*delete*/
                    delFlag =1;
                    if(cur_i <cmd_i)
                    {
                        cmd[cmd_i] = 0;
                        omCmlDeletechar(cmd, cur_i, cmd_i);
                        for(j= cur_i; j< cmd_i-1; j++)
                        {
                            printf(" \b");
                            PT_PRINTCHAR(cmd[j]);
                        }
                        PT_PRINTS(" \b");

                        if(cmd_i -cur_i -1 >0)
                        {
                            PT_MOVELEFT(cmd_i -cur_i -1);
                        }

                        cmd_i --;
                    }

                    break;

				default:
					//printf("this key 3value = 0x%x\n",c);
					break;
			}
        }

    }

    reset_key();
    return 0;
}

//命令行解析
int omCmdlineParse(char  cmd[],  int *argc,  char argv[OM_CML_PARSE_NUM_MAX][OM_CML_PARSE_CHAR_MAX])
{
	char  *p;
	int i,j;
	p = cmd;
	i= 0; j =0;

	if(*p == ' ')
	{
		do
		{
			p++;
		}while(*p == ' ');
	}

	while(*p != '\0')
	{
		while(*p != ' ' && *p!= '\0')
		{
			argv[i][j] = *p;
			p++;
			j++;
		}

		argv[i][j] = '\0';

        if(*p == ' ')
		{
			do
			{
				p++;
			}while(*p == ' ');
		}

		i++;
		j=0;

	}

	*argc = i;

    #ifdef CML_DEBUG

    if (*argc >0)
    {
    	OM_LOG(OM_LOG_LEVEL_DEBG,"cmd parse to argv[](argc is %d): ",*argc);

    	for(i=0;i<*argc;i++)
    	{
    		OM_LOG(OM_LOG_LEVEL_DEBG,"argv[%d] = %s",i,argv[i]);
    	}
    }
    #endif

	return 0;
}

//cli 初始化函数
int cli_init(){
	g_hisList = omCmlHistoryListCreate();
	return 0;
}

//支持ctrl+v复制功能
int cli_setCtrlvProcFunc(PF_CtrlvProc pfCtrlvProc){
	g_pfCtrlvProc = pfCtrlvProc;
	return 0;
}

